#include <iostream>
#include <iomanip>

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gmp.h>

#include "Integer.hpp"
#include "GenericException.hpp"

#define OUTPUT_DECIMAL

Integer::Integer() {
	mpz_init(value);
}

Integer::Integer(unsigned long int value) {
	mpz_init_set_ui(this->value, value);
}

Integer::Integer(const Integer &other) {
	mpz_init_set(this->value, other.value);
}

Integer::Integer(const Block &dataBlock) {
	mpz_init(value);
	mpz_import(value, dataBlock.getLength(), -1, 1, 0, 0, dataBlock.getRO());
}

Integer& Integer::operator=(unsigned long int value) {
	mpz_set_ui(this->value, value);
	return *this;
}

Integer& Integer::operator=(const Integer &other) {
	mpz_set(value, other.value);
	return *this;
}

Integer Integer::operator+(const Integer &other) {
	Integer newInt;
	mpz_add(newInt.value, value, other.value);
	return newInt;
}

Integer Integer::operator+(unsigned long int value) {
	Integer newInt(*this);
	mpz_add_ui(newInt.value, newInt.value, value);
	return newInt;
}

Integer& Integer::operator+=(const Integer &other) {
	mpz_add(this->value, this->value, other.value);
	return *this;
}

Integer& Integer::operator+=(unsigned long int value) {
	mpz_add_ui(this->value, this->value, value);
	return *this;
}

Integer Integer::operator-(const Integer &other) {
	Integer newInt;
	mpz_sub(newInt.value, value, other.value);
	return newInt;
}

Integer Integer::operator-(unsigned long int value) {
	Integer newInt(*this);
	mpz_sub_ui(newInt.value, newInt.value, value);
	return newInt;
}

Integer& Integer::operator-=(const Integer &other) {
	mpz_sub(this->value, this->value, other.value);
	return *this;
}

Integer& Integer::operator-=(unsigned long int value) {
	mpz_sub_ui(this->value, this->value, value);
	return *this;
}

Integer Integer::operator*(const Integer &other) {
	Integer newInt;
	mpz_mul(newInt.value, value, other.value);
	return newInt;
}

Integer Integer::operator*(unsigned long int value) {
	Integer newInt(*this);
	mpz_mul_ui(newInt.value, newInt.value, value);
	return newInt;
}

Integer& Integer::operator*=(const Integer &other) {
	mpz_mul(this->value, this->value, other.value);
	return *this;
}

Integer& Integer::operator*=(unsigned long int value) {
	mpz_mul_ui(this->value, this->value, value);
	return *this;
}

Integer Integer::operator/(const Integer &other) {
	Integer newInt;
	mpz_div(newInt.value, value, other.value);
	return newInt;
}

Integer Integer::operator/(unsigned long int value) {
	Integer newInt(*this);
	mpz_div_ui(newInt.value, newInt.value, value);
	return newInt;
}

Integer& Integer::operator/=(const Integer &other) {
	mpz_div(this->value, this->value, other.value);
	return *this;
}

Integer& Integer::operator/=(unsigned long int value) {
	mpz_div_ui(this->value, this->value, value);
	return *this;
}

bool Integer::operator<(const Integer &other) {
	int cmp;
	cmp = mpz_cmp(value, other.value);
	return cmp < 0;
}

bool Integer::operator<=(const Integer &other) {
	int cmp;
	cmp = mpz_cmp(value, other.value);
	return (cmp < 0) || (cmp == 0);
}

bool Integer::operator>(const Integer &other) {
	int cmp;
	cmp = mpz_cmp(value, other.value);
	return cmp > 0;
}

bool Integer::operator>=(const Integer &other) {
	int cmp;
	cmp = mpz_cmp(value, other.value);
	return (cmp > 0) || (cmp == 0);
}

bool Integer::operator==(const Integer &other) {
	int cmp;
	cmp = mpz_cmp(value, other.value);
	return cmp == 0;
}

bool Integer::operator!=(const Integer &other) {
	int cmp;
	cmp = mpz_cmp(value, other.value);
	return cmp != 0;
}

bool Integer::operator<(unsigned long int other) {
	int cmp;
	cmp = mpz_cmp_ui(value, other);
	return cmp < 0;
}

bool Integer::operator<=(unsigned long int other) {
	int cmp;
	cmp = mpz_cmp_ui(value, other);
	return (cmp < 0) || (cmp == 0);
}

bool Integer::operator>(unsigned long int other) {
	int cmp;
	cmp = mpz_cmp_ui(value, other);
	return cmp > 0;
}

bool Integer::operator>=(unsigned long int other) {
	int cmp;
	cmp = mpz_cmp_ui(value, other);
	return (cmp > 0) || (cmp == 0);
}

bool Integer::operator==(unsigned long int other) {
	int cmp;
	cmp = mpz_cmp_ui(value, other);
	return cmp == 0;
}

bool Integer::operator!=(unsigned long int other) {
	int cmp;
	cmp = mpz_cmp_ui(value, other);
	return cmp != 0;
}

bool Integer::millerRabinMightBePrime(const int repetitions) const {
	return mpz_probab_prime_p(value, repetitions) != 0;
}

Integer &Integer::powMod(const Integer &Exponent, const Integer &Modulo) {
	mpz_powm(value, value, Exponent.value, Modulo.value);
	return *this;
}

void Integer::gcd(Integer &g, const Integer &a, const Integer &b) {
	/*    g = gcd(a, b)    */
	mpz_gcd(g.value, a.value, b.value);
}

void Integer::extendedGcd(Integer &g, Integer &s, Integer &t, const Integer &a, const Integer &b) {
	/*    g = gcd(a, b)            */
	/*    a * s  +  b * t  =  g    */
	mpz_gcdext(g.value, s.value, t.value, a.value, b.value);
}

void Integer::randomize(unsigned int bits) {
	randomInit();
	mpz_urandomb(value, randState, bits);
}

void Integer::randomInit() {
	if (randomInited) return;
	randomInited = true;
	gmp_randinit_default(randState);
	
	int fd = open("/dev/random", O_RDONLY);
	if (fd == -1) {
		return; /* Seeding not possible */
	}

	unsigned long int Seed;
	if (read(fd, &Seed, sizeof(Seed)) != sizeof(Seed)) {
		close(fd);
		return; /* Seeding not possible */
	}

	gmp_randseed_ui(randState, Seed);
	close(fd);
}

Integer::~Integer() {
	mpz_clear(value);
}

void Integer::dump() const {
#ifdef OUTPUT_DECIMAL
	mpz_out_str(stderr, 10, value);
#else
	std::cerr << "0x";
	mpz_out_str(stderr, 16, value);
#endif
}

void Integer::writeToFile(FILE *f) const {
	mpz_out_str(f, 16, value);
	fwrite("\n", 1, 1, f);
}

void Integer::readFromFile(FILE *f) {
	mpz_inp_str(value, f, 16);
}

unsigned long int Integer::getValue() const {
	return mpz_get_ui(value);
}

Block Integer::toBlock() const {
	Block returnVal(mpz_sizeinbase(value, 256), 0);
	mpz_export(returnVal.get(), NULL, -1, 1, 0, 0, value);
	return returnVal;
}

